package middleware.p2p;

import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.apache.log4j.Logger;

/**
 * 	Classe implementante un servizio di Discovery per i Worker.
 * 	In questa implementazione (P2P) il discovery è locale al manager quindi non viene implementato
 * 	nessun sistema di proxy per mascherare la complessità della comunicazione su socket.
 * 	Inoltre i sequence diagram assegnati non prevedono la parte di aggiunta di Worker con richieste provenienti dalla rete.
 */
public class WorkerDiscovery implements Discovery {
	/**
	 *	Vector contenente i TaskDescriptor conosciuti dal Discovery.
	 *	Si è scelto il Vector per ottimizzare i tempi del random-access.
	 */
	private Vector<WorkerDescriptor> workers;
	/**
	 * 	Hashtable contenente gli URL dei documenti WSDL dei Replica Manager dei peer conosciuti.
	 */
	private Map<String,URL> rmTable;
	/**
	 * 	Indice dell'ultimo Worker ritornato.
	 */
	private int lastReturned;
	/**
	 * 	Costruttore di default della classe.
	 * 	Si limita ad inizializzare il Vector.
	 */
	public WorkerDiscovery() {
		Logger log=Logger.getLogger(this.getClass());
		log.info("WorkerDiscovery started");
		this.workers=new Vector<WorkerDescriptor>();
		this.rmTable=new HashMap<String, URL>();
		this.lastReturned=-1;
	}
	/**
	 * 	Metodo per impostare la lista interna dei Worker ad un oggetto esterno di tipo Vector<WorkerDescriptor>
	 * 	@param 	workers	Il nuovo oggetto che rappresenterà la coda interna del Discovery.	
	 */
	public void setWorkers(Vector<WorkerDescriptor> workers) {
		this.workers = workers;
	}
	/**
	 * 	Metodo per ottenere un array contenente tutti i WorkerDescriptor conosciuti dal Discovery
	 * 	@return	Un array contenente tutti i worker conosciuti dal Discovery.
	 */
	public WorkerDescriptor[] getWorkers() {
		WorkerDescriptor tArray[]=new WorkerDescriptor[this.workers.size()];
		this.workers.toArray(tArray);
		return tArray;
	}
	/**
	 * 	Metodo sincronizzato per aggiungere un WorkerDescriptor alla lista dei Worker conosciuti dal Discovery.
	 * 	Il metodo garantisce che durante l'inserimento nessuno andrà a leggere i dati del Discovery.
	 * 	@param	wd	WorkerDescriptor contenente i dati del Worker da aggiungere.
	 * 	@return		L'indice all'interno del Discovery del WorkerDescriptor appena aggiunto.
	 */
	@Override
	public synchronized int addWorker(WorkerDescriptor wd) {
		this.workers.add(wd);		
		Logger.getLogger(this.getClass()).debug("Worker "+wd.getAddr().getIP()+":"+wd.getAddr().getPort()+" added to the Discovery");
		return (this.workers.size()-1);
	}
	/**
	 * 	Metodo per ottere l'indirizzo del Worker adatto all'esecuzione del Task descritto dal TaskDescriptor.
	 * 	In questa implementazione il metodo mantiene un indice dell'ultimo valore ritornato e scorre il vettore in avanti
	 * 	cercando il primo worker libero. Se non vi sono Worker liberi verrà tornato il Worker successivo a quello ritornato
	 * 	l'ultima volta.
	 * 	@param	ts	TaskDescriptor del Task da eseguire e per cui si sta cercando un Worker.
	 * 	@return		L'indirizzo del Worker selezionato altrimenti null se non si trova nessun Worker.
	 */
	@Override
	public Address getWorker(TaskDescriptor ts) {
		if (this.workers.size()>0) {
			int nextWorking=(lastReturned+1)%this.workers.size();
			int totalSize=this.workers.size();
			for (int i=0;i<totalSize;i++) {
				int index=(this.lastReturned+i+1)%totalSize;
				if (this.workers.get(index).getStatus()==WorkerStatus.Free) {
					lastReturned=index;
					Logger.getLogger(this.getClass()).debug("Worker "+this.workers.get(index).getAddr().getIP()+":"
							+this.workers.get(index).getAddr().getPort()+" extracted from the Discovery");
					return this.workers.get(index).getAddr();
				}
			}
			lastReturned = nextWorking;
			Logger.getLogger(this.getClass()).debug("Worker "+this.workers.get(nextWorking).getAddr().getIP()+":"
					+this.workers.get(nextWorking).getAddr().getPort()+" extracted from the Discovery");
			return this.workers.get(nextWorking).getAddr();
		} else {
			Logger.getLogger(this.getClass()).warn("Discovery Vector is Empty");
			return null;
		}
	}
	/**
	 * 	Metodo per aggiornare il descrittore di un Worker conosciuto dal Discovery.
	 * 	@param	index	Indice del WorkerDescriptor da aggiornare.
	 * 	@param	wd		WorkerDescriptor da aggiornare.
	 * 	@return			L'indice del descrittore aggiornato oppure -1 se non esiste l'indice richiesto.
	 */
	@Override
	public synchronized int updateWorker(int index, WorkerDescriptor wd) {
		if (index<this.workers.size()) {
			this.workers.set(index, wd);
			Logger.getLogger(this.getClass()).debug("WorkerDescriptor "+index+" updated.");
			return index;
		} else {
			Logger.getLogger(this.getClass()).debug("WorkerDescriptor "+index+" doesn't exist.");
			return -1;
		}
	}
	/**
	 * 	Metodo per inserire/aggiornare l'URL del documento WSDL del Replica Manager associato ad un
	 * 	componente.
	 * 	@param	cid		Identificativo del componente a cui associare il Replica Manager
	 * 	@param	rmWsdl	URL del WSDL relativo al Replica Manager. 
	 */
	public synchronized void registerRM(String cid,URL rmUrl) {
		this.rmTable.put(cid, rmUrl);
	}
	/**
	 * 	Metodo per ottenere l'URL del WSDL del RM relativo ad un componente.
	 * 	@param	cid	Identificativo del componente del quale si vuole ottenere il WSDL
	 * 	@return	L'URL del WSDL cercato oppure null	
	 */
	public synchronized URL getRM(String cid) {
		if (this.rmTable.containsKey(cid)) {
			return this.rmTable.get(cid);
		} else {
			return null;
		}
	}
	/**
	 * 	Metodo per ottenere tutti gli URL di WSDL conosciuti.
	 * 	@return Tutti gli URL conosciuti.
	 */
	public synchronized URL[] getAllRM() {
		Collection<URL> values=this.rmTable.values();
		URL retArr[]= new URL[values.size()];
		values.toArray(retArr);
		return retArr;	
	}
}